//////////////////////////////////////////////
// File				:	Mouse.h
//
// Author			:	Nick Faletra (NF)
//
// Date Created		:	12/22/2013
//////////////////////////////////////////////
#include "Joystick.h"
#include <stdio.h>

CJoystick::CJoystick(IDirectInput8* pDI, HWND hWnd, const DIDEVICEINSTANCE* pdidi, bool bExclusive)
{
	char szErrorBuffer[256];

	strcpy_s(m_szJoyName, _countof(m_szJoyName), (char*)pdidi->tszProductName);
	
	m_bXbox360Pad = (strcmp(m_szJoyName, "Xbox 360 Wireless Receive for Windows") == 0 || strcmp(m_szJoyName, "Controller (XBOX 360 For Windows)") == 0);

	if (FAILED(pDI->CreateDevice(pdidi->guidInstance, &m_pDevice, nullptr)))
	{
		sprintf_s(szErrorBuffer, _countof(szErrorBuffer), "Failed to create Joystick device: %s", m_szJoyName);
		DIERRBOX(hWnd, szErrorBuffer);
	}

	if (FAILED(m_pDevice->SetDataFormat(&c_dfDIJoystick2)))
	{
		sprintf_s(szErrorBuffer, _countof(szErrorBuffer), "Failed to set data format on Joystick: %s", m_szJoyName);
		DIERRBOX(hWnd, szErrorBuffer);
	}

	DIDEVCAPS didCaps;
	memset(&didCaps, 0, sizeof(didCaps));
	didCaps.dwSize = sizeof(didCaps);
	m_pDevice->GetCapabilities(&didCaps);

	if (didCaps.dwFlags & DIDC_FORCEFEEDBACK)
		bExclusive = true;

	DWORD axes = didCaps.dwAxes;
	DWORD povs = didCaps.dwPOVs;

	m_nNumButtons = didCaps.dwButtons;

	DWORD dwFlags = DISCL_FOREGROUND;
	dwFlags |= bExclusive ? DISCL_EXCLUSIVE : DISCL_NONEXCLUSIVE;

	if (FAILED(m_pDevice->SetCooperativeLevel(hWnd, dwFlags)))
	{
		sprintf_s(szErrorBuffer, _countof(szErrorBuffer), "Failed to set cooperative level on Joystick: %s", m_szJoyName);
		DIERRBOX(hWnd, szErrorBuffer);
	}

	DIPROPRANGE dipr;
	dipr.diph.dwSize		= sizeof(DIPROPRANGE);
	dipr.diph.dwHeaderSize	= sizeof(DIPROPHEADER);
	dipr.diph.dwHow			= DIPH_BYOFFSET;
	dipr.lMin				= -JOYSTICK_AXIS_RANGE;
	dipr.lMax				= JOYSTICK_AXIS_RANGE;

	dipr.diph.dwObj			= DIJOFS_X;
	m_pDevice->SetProperty(DIPROP_RANGE, &dipr.diph);

	dipr.diph.dwObj			= DIJOFS_Y;
	m_pDevice->SetProperty(DIPROP_RANGE, &dipr.diph);

	if (!m_bXbox360Pad)
	{
		dipr.diph.dwObj = DIJOFS_Z;
		m_pDevice->SetProperty(DIPROP_RANGE, &dipr.diph);

		dipr.diph.dwObj = DIJOFS_RZ;
		m_pDevice->SetProperty(DIPROP_RANGE, &dipr.diph);
	}
	else
	{
		dipr.diph.dwObj = DIJOFS_Z;
		m_pDevice->SetProperty(DIPROP_RANGE, &dipr.diph);

		dipr.diph.dwObj = DIJOFS_RX;
		m_pDevice->SetProperty(DIPROP_RANGE, &dipr.diph);

		dipr.diph.dwObj = DIJOFS_RY;
		m_pDevice->SetProperty(DIPROP_RANGE, &dipr.diph);
	}

	DIPROPDWORD deadZone;
	deadZone.diph.dwSize		= sizeof(deadZone);
	deadZone.diph.dwHeaderSize	= sizeof(deadZone.diph);
	deadZone.diph.dwObj			= DIJOFS_X;
	deadZone.diph.dwHow			= DIPH_BYOFFSET;
	deadZone.dwData				= 1000;

	m_pDevice->SetProperty(DIPROP_DEADZONE, &deadZone.diph);

	deadZone.diph.dwObj = DIJOFS_Y;
	m_pDevice->SetProperty(DIPROP_DEADZONE, &deadZone.diph);

	if (!m_bXbox360Pad)
	{
		deadZone.diph.dwObj = DIJOFS_Z;
		m_pDevice->SetProperty(DIPROP_DEADZONE, &deadZone.diph);

		deadZone.diph.dwObj = DIJOFS_RZ;
		m_pDevice->SetProperty(DIPROP_DEADZONE, &deadZone.diph);
	}
	else
	{
		deadZone.diph.dwObj = DIJOFS_RX;
		m_pDevice->SetProperty(DIPROP_DEADZONE, &deadZone.diph);

		deadZone.diph.dwObj = DIJOFS_RY;
		m_pDevice->SetProperty(DIPROP_DEADZONE, &deadZone.diph);
	}

	DIPROPDWORD dipdw;
	memset(&dipdw, 0, sizeof(dipdw));
	
	dipdw.diph.dwSize		= sizeof(DIPROPDWORD);
	dipdw.diph.dwHeaderSize	= sizeof(DIPROPHEADER);
	dipdw.diph.dwObj		= 0;
	dipdw.diph.dwHow		= DIPH_DEVICE;
	dipdw.dwData			= BUFFER_SIZE;

	if (FAILED(m_pDevice->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)))
	{
		sprintf_s(szErrorBuffer, _countof(szErrorBuffer), "Could not Set the Property for Buffered Input for Joystick: %s", m_szJoyName);
		DIERRBOX(hWnd, szErrorBuffer);
	}

	memset(&m_diJoyState, 0, sizeof(m_diJoyState));
	memset(&m_diPrevJoyState, 0, sizeof(m_diPrevJoyState));

	m_bUnplugged = false;
	
	SwapRStickAxes(false);
}

bool CJoystick::ReadDevice()
{
	if (!m_pDevice)
		return false;

	memcpy(&m_diPrevJoyState, &m_diJoyState, sizeof(m_diPrevJoyState));
	
	memset(&m_diJoyState, 0, sizeof(m_diJoyState));

	if (FAILED(m_pDevice->Poll()))
	{
		HRESULT hr = m_pDevice->Acquire();

		if (!m_bUnplugged)
			if (hr == DIERR_UNPLUGGED)
				m_bUnplugged = true;

		if (m_bUnplugged)
			if (SUCCEEDED(m_pDevice->Acquire()))
				m_bUnplugged = true;

		if (hr == DIERR_INPUTLOST)
			hr = m_pDevice->Acquire();
	}

	if (FAILED(m_pDevice->GetDeviceState(sizeof(m_diJoyState), (void*)&m_diJoyState)))
		return false;

	return true;
}

bool CJoystick::ReadBufferedDevice()
{
	if (m_pDevice)
		return false;

	ClearBufferedData();

	if (FAILED(m_pDevice->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), m_didod, &m_dwBufferElements, 0)))
	{
		if (FAILED(m_pDevice->Acquire()))
			return false;

		ClearBufferedData();

		if (FAILED(m_pDevice->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), m_didod, &m_dwBufferElements, 0)))
			return false;
	}

	return true;
}

void CJoystick::SwitchRStickAxes()
{
	m_bZAxisY = !m_bZAxisY;
}

bool CJoystick::ButtonDown(unsigned char ucButton)
{
	return (m_diJoyState.rgbButtons[ucButton] & 0x80) ? true : false;
}

bool CJoystick::ButtonPressed(unsigned char ucButton)
{
	return ButtonDown(ucButton) && !(m_diPrevJoyState.rgbButtons[ucButton] & 0x80);
}

bool CJoystick::ButtonPressedEx(unsigned char ucButton)
{
	for (DWORD i = 0; i < m_dwBufferElements; ++i)
		if (m_didod[i].dwOfs == (DWORD)DIJOFS_BUTTON(ucButton) && m_didod[i].dwData & 0x80)
			return true;

	return false;
}

bool CJoystick::ButtonUp(unsigned char ucButton)
{
	return !ButtonDown(ucButton);
}

bool CJoystick::ButtonReleased(unsigned char ucButton)
{
	return ButtonUp(ucButton) && (m_diPrevJoyState.rgbButtons[ucButton] & 0x80);
}

bool CJoystick::ButtonReleasedEx(unsigned char ucButton)
{
	for (DWORD i = 0; i < m_dwBufferElements; ++i)
		if (m_didod[i].dwOfs == (DWORD)DIJOFS_BUTTON(ucButton) && !(m_didod[i].dwData & 0x80))
			return true;

	return false;
}

bool CJoystick::TranslatePOV(int nDir, DWORD dwPOVDir)
{
	bool bPOVCentered = LOWORD(dwPOVDir) == 0xFFFF;

	if (bPOVCentered)
		return false;

	int nPOV = (int)LOWORD(dwPOVDir);

	switch(nDir)
	{
	case DIR_UP:
		{
			return (nPOV > POV_LEFT || nPOV < POV_RIGHT);
		}
		break;

	case DIR_DOWN:
		{
			return (nPOV > POV_RIGHT && nPOV < POV_LEFT);
		}
		break;

	case DIR_LEFT:
		{
			return nPOV > POV_DOWN;
		}
		break;

	case DIR_RIGHT:
		{
			return (nPOV > POV_UP && nPOV < POV_DOWN);
		}
		break;
	};

	return false;
}

long CJoystick::TranslateRStickX(DIJOYSTATE2& diJoyState)
{
	if (!m_pDevice)
		return 0;

	if (!m_bXbox360Pad)
		return m_bZAxisY ? diJoyState.lRz : diJoyState.lZ;
	
	return diJoyState.lRx;
}

long CJoystick::TranslateRStickY(DIJOYSTATE2& diJoyState)
{
	if (!m_pDevice)
		return 0;

	if (!m_bXbox360Pad)
		return m_bZAxisY ? diJoyState.lZ : diJoyState.lRz;

	return diJoyState.lRy;
}

bool CJoystick::DPadDown(int nDir)
{
	return TranslatePOV(nDir, m_diJoyState.rgdwPOV[0]);
}

bool CJoystick::DPadPressed(int nDir)
{
	return DPadDown(nDir) && !TranslatePOV(nDir, m_diPrevJoyState.rgdwPOV[0]);
}

bool CJoystick::DPadUp(int nDir)
{
	return !DPadDown(nDir);
}

bool CJoystick::DPadReleased(int nDir)
{
	return !DPadDown(nDir) && TranslatePOV(nDir, m_diPrevJoyState.rgdwPOV[0]);
}

bool CJoystick::GetLStickDirDown(int nDir)
{
	if (!m_pDevice)
		return false;

	switch(nDir)
	{
	case DIR_LEFT:
		{
			if (m_diJoyState.lX < 0)
				return true;
		}
		break;

	case DIR_RIGHT:
		{
			if (m_diJoyState.lX > 0)
				return true;
		}
		break;

	case DIR_UP:
		{
			if (m_diJoyState.lY < 0)
				return true;
		}
		break;

	case DIR_DOWN:
		{
			if (m_diJoyState.lY > 0)
				return true;
		}
		break;
	};

	return false;
}

bool CJoystick::GetLStickDirPressed(int nDir)
{
	if (!m_pDevice)
		return false;

	switch(nDir)
	{
	case DIR_LEFT:
		{
			if (m_diJoyState.lX < -JOYSTICK_THRESHOLD && !(m_diPrevJoyState.lX < -JOYSTICK_THRESHOLD))
				return true;
		}
		break;

	case DIR_RIGHT:
		{
			if (m_diJoyState.lX > JOYSTICK_THRESHOLD && !(m_diPrevJoyState.lX > JOYSTICK_THRESHOLD))
				return true;
		}
		break;

	case DIR_UP:
		{
			if (m_diJoyState.lY < -JOYSTICK_THRESHOLD && !(m_diPrevJoyState.lY < -JOYSTICK_THRESHOLD))
				return true;
		}
		break;

	case DIR_DOWN:
		{
			if (m_diJoyState.lY > JOYSTICK_THRESHOLD && !(m_diPrevJoyState.lY > JOYSTICK_THRESHOLD))
				return true;
		}
		break;
	};

	return false;
}

int CJoystick::GetLStickXAmount()
{
	if (!m_pDevice)
		return 0;

	return m_diJoyState.lX;
}

int CJoystick::GetLStickYAmount()
{
	if (!m_pDevice)
		return 0;

	return m_diJoyState.lY;
}

float CJoystick::GetLStickXNormalized()
{
	return (float)GetLStickXAmount() * ONE_OVER_RANGE;
}

float CJoystick::GetLStickYNormalized()
{
	return (float)GetLStickYAmount() * ONE_OVER_RANGE;
}

bool CJoystick::GetRStickDirDown(int nDir)
{
	if (!m_pDevice)
		return false;

	switch(nDir)
	{
	case DIR_LEFT:
		{
			if (TranslateRStickX(m_diJoyState) < 0)
				return true;
		}
		break;

	case DIR_RIGHT:
		{
			if (TranslateRStickX(m_diJoyState) > 0)
				return true;
		}
		break;

	case DIR_UP:
		{
			if (TranslateRStickY(m_diJoyState) < 0)
				return true;
		}
		break;

	case DIR_DOWN:
		{
			if (TranslateRStickY(m_diJoyState) > 0)
				return true;
		}
		break;
	};

	return false;
}

bool CJoystick::GetRStickDirPressed(int nDir)
{
	if (!m_pDevice)
		return false;

	switch(nDir)
	{
	case DIR_LEFT:
		{
			if (TranslateRStickX(m_diJoyState) < -JOYSTICK_THRESHOLD && !(TranslateRStickX(m_diPrevJoyState) < -JOYSTICK_THRESHOLD))
				return true;
		}
		break;

	case DIR_RIGHT:
		{
			if (TranslateRStickX(m_diJoyState) > JOYSTICK_THRESHOLD && !(TranslateRStickX(m_diPrevJoyState) > JOYSTICK_THRESHOLD))
				return true;
		}
		break;

	case DIR_UP:
		{
			if (TranslateRStickY(m_diJoyState) < -JOYSTICK_THRESHOLD && !(TranslateRStickY(m_diPrevJoyState) < -JOYSTICK_THRESHOLD))
				return true;
		}
		break;

	case DIR_DOWN:
		{
			if (TranslateRStickY(m_diJoyState) > JOYSTICK_THRESHOLD && !(TranslateRStickY(m_diPrevJoyState) > JOYSTICK_THRESHOLD))
				return true;
		}
		break;
	};

	return false;
}

int CJoystick::GetRStickXAmount()
{
	if (!m_pDevice)
		return 0;

	return TranslateRStickX(m_diJoyState);
}

int CJoystick::GetRStickYAmount()
{
	if (!m_pDevice)
		return 0;

	return TranslateRStickY(m_diJoyState);
}

float CJoystick::GetRStickXNormalized()
{
	return (float)GetRStickXAmount() * ONE_OVER_RANGE;
}

float CJoystick::GetRStickYNormalized()
{
	return (float)GetRStickYAmount() * ONE_OVER_RANGE;
}

int CJoystick::GetLTriggerAmount()
{
	if (!m_bXbox360Pad)
		return 0;

	return m_diJoyState.lZ;
}

int CJoystick::GetRTriggerAmount()
{
	if (!m_bXbox360Pad)
		return 0;

	return -m_diJoyState.lZ;
}

float CJoystick::GetLTriggerNormalized()
{
	return GetLTriggerAmount() * ONE_OVER_RANGE;
}

float CJoystick::GetRTriggerNormalized()
{
	return GetRTriggerAmount() * ONE_OVER_RANGE;
}

int CJoystick::CheckBufferedButtons()
{
	int button = -1;

	for (int j = 0; j < GetNumButtons(); ++j)
	{
		if (ButtonPressed((unsigned char)j))
		{
			button = j;
			break;
		}
	}

	return button;
}

int CJoystick::CheckBufferedButtonsEx()
{
	int button = -1;

	for (DWORD i = 0; i < m_dwBufferElements; ++i)
	{
		for (int j = 0; j < GetNumButtons(); ++j)
		{
			if (m_didod[i].dwOfs == (DWORD)DIJOFS_BUTTON(j) && (m_didod[i].dwData & 0x80))
			{
				button = j;
				break;
			}
		}
	}

	return button;
}